package com.javabi.common.concurrent.collect.copyonwrite;

import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.google.common.collect.ForwardingList;

public abstract class CopyOnWriteList<E> extends ForwardingList<E> {

	private transient final Lock lock = new ReentrantLock();
	private volatile List<E> delegate = emptyList();

	@Override
	protected List<E> delegate() {
		return delegate;
	}

	@Override
	public Iterator<E> iterator() {
		return unmodifiableList(this.delegate).iterator();
	}

	@Override
	public ListIterator<E> listIterator() {
		return unmodifiableList(this.delegate).listIterator();
	}

	@Override
	public ListIterator<E> listIterator(int index) {
		return unmodifiableList(this.delegate).listIterator(index);
	}

	@Override
	public List<E> subList(int fromIndex, int toIndex) {
		return unmodifiableList(this.delegate.subList(fromIndex, toIndex));
	}

	@Override
	public void clear() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			this.delegate = emptyList();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean add(E element) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			List<E> newList = newList(this.delegate);
			boolean added = newList.add(element);
			this.delegate = newList;
			return added;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void add(int index, E element) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			List<E> newList = newList(this.delegate);
			newList.add(index, element);
			this.delegate = newList;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean addAll(Collection<? extends E> addCollection) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			List<E> newList = newList(this.delegate);
			boolean added = newList.addAll(addCollection);
			this.delegate = newList;
			return added;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean addAll(int index, Collection<? extends E> addCollection) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			List<E> newList = newList(this.delegate);
			boolean added = newList.addAll(index, addCollection);
			this.delegate = newList;
			return added;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean remove(Object element) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			List<E> newList = newList(this.delegate);
			boolean removed = newList.remove(element);
			this.delegate = newList;
			return removed;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean removeAll(Collection<?> removeCollection) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			List<E> newList = newList(this.delegate);
			boolean removed = newList.removeAll(removeCollection);
			this.delegate = newList;
			return removed;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean retainAll(Collection<?> retainCollection) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			List<E> newList = newList(this.delegate);
			boolean removed = newList.retainAll(retainCollection);
			this.delegate = newList;
			return removed;
		} finally {
			lock.unlock();
		}
	}

	protected abstract List<E> newList(List<E> set);

}
